package network;
import gui.GUIHandler;
import gui.GUIManager;

import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;

import javax.swing.SwingUtilities;

import network.enums.ClientPackets;
import network.enums.ServerPackets;
import network.requests.RegisterRequest;

import chat.ChatHandler;

import room.Room;
import room.RoomHandler;
import user.User;
import user.UserData;

import app.AppGlobals;

public class GameServerClient extends Thread
{
	public Socket connection = null;
	public DataInputStream receive = null;
	public DataOutputStream send = null;
	public Boolean isConnected = false;
	String ip = "";
	int port = -1;
	public Date requestTime = null;
	
	public GameServerClient(String ip, int port) {
		this.ip = ip;
		this.port = port;
	}

	public void run()
	{
		if(ip != "" && port != -1){
			if(connect(this.ip, this.port));
				updateLoop();
		}
	}
	
	public boolean connect(String server,int port)
	{

		try {
			this.connection = new Socket(server,port);
			this.receive = new DataInputStream(new BufferedInputStream(connection.getInputStream()));
			this.send = new DataOutputStream(connection.getOutputStream());
		} catch (IOException e) {
			System.out.println("Failed to connect to server.");
			return false;
		}

		isConnected = true;
		System.out.println("Connected to " + server + " Port: " + port);

		return true;
	}
	
	public void disconnect()
	{
		isConnected = false;
		ChatHandler.writeSystemMessage("Disconnected from server.");
		try {
			this.send.close();
			this.receive.close();
			this.connection.close();		
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendChatMessage(String message,String channel,int type,int command){
		try {
			sendPacket(ServerPackets.ChatMessage,AppGlobals.gson.toJson(
								new ChatMessage(message, channel, type, command)
								).getBytes("utf-8"));
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendPacket(ServerPackets type)
	{
		if(type == ServerPackets.Ping)requestTime = new Date();
		sendPacket(new byte[] { (byte)type.getValue() });
	}
	
	public void sendPacket(ServerPackets type, byte[] data)
	{
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		ByteBuffer bb = ByteBuffer.allocate(2);
		bb.order(ByteOrder.LITTLE_ENDIAN);
		bb.putShort((short)(data.length));

		byte[] size = bb.array();
		
		try 
		{
			os.write((byte)type.getValue());
			os.write(size);
	    	os.write(data);
	    	sendPacket(os.toByteArray());
	    	os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	private void sendPacket(byte[] data)
	{
		try 
		{
			if(data != null && isConnected)
			{
				this.send.write(data);
				System.out.println("Packet Sent: " + ServerPackets.fromInt(data[0]));
			}
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	
	private boolean isLargePacket(ClientPackets packet)
	{
		return packet == ClientPackets.GameList || packet == ClientPackets.UserList 
				|| packet == ClientPackets.FriendList || packet == ClientPackets.TeamList;
	}
	
	private boolean isOneByte(ClientPackets packet)
	{
		return (packet == ClientPackets.Banned || packet == ClientPackets.LoginFailed || 
				packet == ClientPackets.RegisterAccept || packet == ClientPackets.RegisterFailed ||
				packet == ClientPackets.Pong || packet == ClientPackets.Kicked);
	}
	
	private void updateLoop(){
		while(isConnected)
		{
			try 
			{
				byte[] packet = new byte[1];
				this.receive.read(packet,0,1);
				final ClientPackets packettype = ClientPackets.fromInt(packet[0]);
				byte[] packetsize = null;
				int len = 0;
				if(!isOneByte(packettype))
				{
					if(isLargePacket(packettype))
					{
						packetsize = new byte[4];
						this.receive.read(packetsize, 0, 4);
						len = ByteBuffer.wrap(packetsize).order(ByteOrder.LITTLE_ENDIAN).getInt();
						if(len == 0){
							disconnect();
							break;
						}
					}
					else
					{	
						packetsize = new byte[2];
						this.receive.read(packetsize, 0, 2);
						len =  ByteBuffer.wrap(packetsize).order(ByteOrder.LITTLE_ENDIAN).getShort();
					}
				}
				
				System.out.println("Packet Length: " + len);
				final byte[] content = new byte[len];
				if(len > 0)
				{
					int bytesobtained = 0;
					while(bytesobtained < len)
					{
						int readlength = this.receive.read(content, bytesobtained,len - bytesobtained);
						if(readlength == -1)
							break;
						bytesobtained += readlength;
					};

					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							onCommand(packettype,content);
						}
					});
				
				}
				else
				{
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							onCommand(packettype,null);
						}
					});
				}
				
				
			} 
			catch (IOException e)
			{
				disconnect();
				e.printStackTrace();
			}
		}
	}
	
	
	public void onCommand(ClientPackets cmd, byte[] raw)
	{
		System.out.println("Packet Received: " + cmd.toString());
		
		if(cmd == ClientPackets.LoginAccepted){ 
			String logindata = new String(raw,Charset.forName("UTF-8"));

			LoginData data = AppGlobals.gson.fromJson(logindata, LoginData.class);
			//TODO: change loginkey to id
			UserData localUser = new UserData(AppGlobals.localUserName,data.LoginKey,data.UserRank);
			AppGlobals.localUser = localUser;
			
			GUIManager.mainWindow.loadMainTabs();
			AppGlobals.launcherConfig.defaultUsername = AppGlobals.localUserName;
			
		}
        else if (cmd == ClientPackets.LoginFailed){
        	//no data is given just tell the user!
        	GUIHandler.launchLoginFailedDialog();
        	
        }
        else if (cmd == ClientPackets.RegisterAccept){
        	//no data is given just tell the user!
        	GUIHandler.launchRegisterSuccessDialog();
        }
        else if (cmd == ClientPackets.RegisterFailed){
        	//no data is given just tell the user!
        	GUIHandler.launchRegisterFailedDialog();
        }
        else if (cmd == ClientPackets.GameList){
        	System.out.println(new String(raw,Charset.forName("UTF-8")));
        	RoomHandler.addRoomArray(
        			AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			Room[].class));
        }
        else if (cmd == ClientPackets.RemoveRoom){
        	RoomHandler.removeRoomEntry(new String(raw,Charset.forName("UTF-8")));
        }
        else if (cmd == ClientPackets.UpdatePlayers){
        	RoomHandler.updateRoom(
        			AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			Room.class));
        }
        else if (cmd == ClientPackets.RoomStart){
        	RoomHandler.onRoomStart(new String(raw,Charset.forName("UTF-8")));
        }
        else if (cmd == ClientPackets.Pong){
        	Date currentTime = new Date();     
    		//in milliseconds
    		long diff = currentTime.getTime() - requestTime.getTime();
    		ChatHandler.writeSystemMessage("Pong! "+ diff + " ms");
        }
        else if (cmd == ClientPackets.ServerMessage){
        	String message = new String(raw,Charset.forName("UTF-8"));
        }
        else if(cmd == ClientPackets.Message){
        	System.out.println(new String(raw,Charset.forName("UTF-8")));
        	ChatHandler.writeMessage(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			ChatMessage.class));
        }
        else if(cmd == ClientPackets.UserList){
        	ChatHandler.addUserList(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			UserData[].class));
        }
        else if(cmd == ClientPackets.AddUser){
        	ChatHandler.addUser(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			UserData.class));
        }
        else if(cmd == ClientPackets.RemoveUser){
        	ChatHandler.removeUser(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			LogoutData.class));
        }
        else if(cmd == ClientPackets.FriendList){
        	ChatHandler.addFriendList(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			String[].class));
        }
        else if(cmd == ClientPackets.TeamList){
        	ChatHandler.addTeamList(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			String[].class));
        }
        else if(cmd == ClientPackets.GameServers){
        	AppGlobals.serverList.clear();
        	ServerInfo[] servers = AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			ServerInfo[].class);
        	for (int i = 0; i < servers.length; i++)
        		AppGlobals.serverList.put(servers[i].serverName, servers[i]);
        }
        else if(cmd == ClientPackets.AddServer){
        	ServerInfo server = AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			ServerInfo.class);
        	AppGlobals.serverList.put(server.serverName, server);
        }
        else if(cmd == ClientPackets.RemoveServer){
        	String serverName = new String(raw,Charset.forName("UTF-8"));
        	if(AppGlobals.serverList.containsKey(serverName))
        		AppGlobals.serverList.remove(serverName);
        }
        else if(cmd == ClientPackets.JoinChannelAccept){
        	ChatHandler.createChatTab(new String(raw,Charset.forName("UTF-8")),false,false);
        }
        else if(cmd == ClientPackets.ChannelList){
        	ChatHandler.createChannelWindow(AppGlobals.gson.fromJson(
        			new String(raw,Charset.forName("UTF-8")), 
        			ChannelData[].class));
        }
        else{
        	//error handling here
        	System.out.println("Unknown Packet.");
        	if(raw != null)
        		System.out.println(new String(raw,Charset.forName("UTF-8")));
        }
		
	}
}
